#ifdef MAODV

/*****************************************************************************
 *
 * Copyright (C) 2003 CSHCN, University of Maryland.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors: Dionysus Blazakis <dblaze@umd.edu>
 *          Jonathan Polley <jpolley@umd.edu>
 *          Dan Rusk <drusk@umd.edu>
 *          Manish Karir <karir@umd.edu>
 *          Jonathan McGee <mcgee@umd.edu>
 *
 *****************************************************************************/

#ifdef NS_PORT
#include "aodv-uu.h"
#else
#include <netinet/in.h>
#include "maodv_mact.h"
#include "maodv_grph.h"
#include "aodv_socket.h"
#include "params.h"
#include "seek_list.h"
#include "defs.h"
#include "debug.h"
#include "multicast_routing_table.h"
#include "packet_queue.h"
#endif

MACT *mact_create(u_int8_t flags, u_int8_t hcnt, u_int32_t src_addr, 
    u_int32_t grp_addr)
{
    MACT *mact;

    mact=(MACT *)aodv_socket_new_msg();

    mact->type=MAODV_MACT;
    mact->res1=0;
    mact->res2=0;
    mact->hcnt=hcnt;
    mact->grp_addr=htonl(grp_addr);
    mact->src_addr=htonl(src_addr);
    mact->src_seqno=htonl(++this_host.seqno);

    /* handle flags */
    if(flags & MACT_JOIN)
        mact->j=1;
    if(flags & MACT_PRUNE)
        mact->p=1;
    if(flags & MACT_GRPL)
        mact->g=1;
    if(flags & MACT_UPDATE)
        mact->u=1;
    if(flags & MACT_REBOOT)
        mact->r=1;

    DEBUG(LOG_DEBUG, 0, "Assembled MACT %s", ip_to_str(grp_addr));

    return mact;
}

void mact_send(u_int8_t flags, u_int8_t hcnt, u_int32_t grp_addr, u_int32_t dst_addr, int ttl)
{
    MACT *mact;
    rt_table_t *route;
    int i;

    route=rt_table_find(dst_addr);
    i=route->ifindex;
    mact=mact_create(flags, hcnt, DEV_IFINDEX(i).ipaddr, grp_addr);

    aodv_socket_send((AODV_msg *)mact, dst_addr, MACT_SIZE, ttl, &DEV_IFINDEX(i));
}

void mact_mcast(u_int8_t flags, u_int8_t hcnt, u_int32_t grp_addr, int ttl)
{
    MACT *mact;
    mrt_table_t *group_rec;
    mrt_nexthop_t *nexthop;
    rt_table_t *route;
    /* prevent sending packet twice on an iface */
    static char ifmask[MAX_NR_INTERFACES];
    int i;

    bzero(ifmask, sizeof(ifmask)*sizeof(char));

    group_rec=mrt_table_find(grp_addr);
    nexthop=group_rec->nexthops;

    /* an attempt to send multicast packets right.
     * destination is group address
     * only send one per interface you are currently multicasting to group on
     */
    while(nexthop!=NULL) {
        route=rt_table_find(nexthop->addr);
        i=route->ifindex;
        if (!DEV_IFINDEX(i).enabled)
            continue;
        if (!ifmask[i]) {
            mact=mact_create(flags, hcnt, DEV_IFINDEX(i).ipaddr, grp_addr);
            aodv_socket_send((AODV_msg *)mact, grp_addr, MACT_SIZE, ttl, &DEV_IFINDEX(i));
            ifmask[i]=1;
        }
        nexthop=nexthop->next;
    }
}

void mact_process(MACT *mact)
{
    u_int32_t src_addr, grp_addr, src_seqno;
    u_int8_t hcnt;
    mrt_table_t *group_rec;

    src_addr=ntohl(mact->src_addr);
    grp_addr=ntohl(mact->grp_addr);
    src_seqno=ntohl(mact->src_seqno);
    hcnt=mact->hcnt;

    group_rec=mrt_table_find(grp_addr);

    if(group_rec == NULL) {
        /* shouldn't happen */
        return;
    }

    if(mact->j) {
        /* JOIN */
        /* sender wants to join specified group with us as next hop.
         * if we are already in group, then fine, else need to
         * pseudo-join ourselves.
         */
        mrt_nexthop_t *nh;
        
        nh=mrt_nexthop_find(group_rec, src_addr);
        if(nh==NULL){
            nh=mrt_nexthop_add(group_rec, src_addr, MRT_DOWNSTREAM, src_seqno);
        }
        if(nh->activated){
            mrt_nexthop_deactivate(group_rec, nh->addr);
        }
        if ( nh->direction==MRT_UPSTREAM) {
            nh->direction=MRT_DOWNSTREAM;
        }
        mrt_nexthop_activate(group_rec, src_addr);
        DEBUG(LOG_DEBUG, 0, "activated downstream %s", ip_to_str(src_addr));

        /* check if we are not leader and yet have no upstream*/
        if(!group_rec->am_leader && group_rec->activated_upstream_cnt==0) {
            /* not leader and no upstream, so activate one */
            mact_activate_best_upstream(group_rec);
        }


    }else if(mact->p) {
        /* PRUNE */
        mrt_nexthop_t *nexthop;
        
        DEBUG(LOG_DEBUG, 0, "Entered mact->p handler");
       
        nexthop=mrt_table_get_activated_upstream(group_rec);
        if(nexthop!=NULL && nexthop->addr==src_addr){
            DEBUG(LOG_DEBUG, 0, "\tUpstream sent prune");
            /* deactivate said upstream in our records */
            mrt_nexthop_deactivate(group_rec, nexthop->addr);
            /* upstream sent us prune */
            /* if member, become leader, else leave and make a downstream */
            if(group_rec->is_member || (group_rec->is_gateway
                        && group_rec->activated_downstream_cnt > 0)){
                mrt_table_become_leader(group_rec);
            }else{
                mact_make_downstream_leader(group_rec);
            }
        }

        /* if no other downstreams and not listening ourselves, prune self */
        else if(!group_rec->is_member
                && (!group_rec->is_gateway || group_rec->activated_upstream_cnt == 0)
                && (group_rec->activated_downstream_cnt == 1)) {
            mrt_nexthop_t *upstream;
            /* Gateway code can create non-member leaders */
            group_rec->is_gateway = 0;
            group_rec->am_leader = 0;
            upstream=mrt_table_get_activated_upstream(group_rec);
            DEBUG(LOG_DEBUG, 0, "\tDownstream sent prune, prune self");
            if(upstream != NULL){
                mact_send(MACT_PRUNE, 1, grp_addr, upstream->addr,1);
                mrt_nexthop_deactivate(group_rec, upstream->addr);
            }
            mrt_table_stop_routing(group_rec);
        }else{
            DEBUG(LOG_DEBUG, 0, "\tDownstream sent prune, stay on tree");
        }

        /* remove them */
        mrt_nexthop_deactivate(group_rec, src_addr);
    } else if(mact->g) {
        /* GR/OUP LEADER
         * 
         * my upstream may send it to me.
         * either I become leader or I promote a child
         */

        /* node who sent it has me marked as its upstream now */
        mrt_nexthop_t *node;
        node=mrt_table_get_activated_upstream(group_rec);
        if(node!=NULL){
            node->direction=MRT_DOWNSTREAM;
            group_rec->activated_upstream_cnt--;
            group_rec->activated_downstream_cnt++;
        }
        
        if(group_rec->is_member) {
            /* declare myself leader */
            /* TODO: might need to set U flag on first one */

            mrt_table_become_leader(group_rec);
        } else {
            /* send a notice to a downstream making it leader */
            mact_make_downstream_leader(group_rec);
        }
    } else if(mact->u) {
        /* UPDATE */
    } else if(mact->r) {
        /* REBOOT */
    }
}

mrt_nexthop_t *mact_activate_best_upstream(mrt_table_t *mrt){
    mrt_nexthop_t *besthop=NULL;

    besthop=mrt_table_get_best_upstream(mrt);

    /* and activate it */
    if(besthop!=NULL){
        mrt_table_start_routing(mrt);

        mrt_nexthop_activate(mrt, besthop->addr);
        mact_send(MACT_JOIN, 1, mrt->grp_addr, besthop->addr, 1);
        DEBUG(LOG_DEBUG, 0, "activated upstream %s", ip_to_str(besthop->addr));

        if ( seek_list_remove(seek_list_find(mrt->grp_addr) ) ) {
            mcast_packet_queue_send( mrt->grp_addr );
        }
    }

    return besthop;
}

void mact_make_downstream_leader(mrt_table_t *group_rec){
    mrt_nexthop_t *downstream;
    DEBUG(LOG_DEBUG, 0, "Entered mact_make_downstream_leader");
    
    if(group_rec->activated_downstream_cnt==0){
        DEBUG(LOG_DEBUG, 0, "No downstreams to make leader!  Clearing flags");
        mrt_table_stop_routing(group_rec);
        group_rec->is_gateway = 0;
        return;
    }
    
    downstream=group_rec->nexthops;
    while(downstream->direction!=MRT_DOWNSTREAM || !downstream->activated){
        downstream=downstream->next;
    }
    
    if(group_rec->activated_downstream_cnt==1){
        /* one child, make him leader */
        DEBUG(LOG_DEBUG, 0, "\tEntered one child version");
        mact_send(MACT_PRUNE, 0, group_rec->grp_addr, downstream->addr,1);
        mrt_nexthop_deactivate(group_rec, downstream->addr);
        mrt_table_stop_routing(group_rec);

        /* remove any pending RREQs (or other packets) for this group */
        seek_list_remove(seek_list_find(group_rec->grp_addr));
        packet_queue_drop(group_rec->grp_addr);

        /* flush all nexthops */
        mrt_nexthop_remove_all(group_rec);
    }else{
        /* send a 'G' mact to an arbitrary child, then make it upstream */
        DEBUG(LOG_DEBUG, 0, "\tEntered many child version (where you stay as router)");
        mact_send(MACT_GRPL, 0, group_rec->grp_addr, downstream->addr,1);
        downstream->direction=MRT_UPSTREAM;
        group_rec->activated_downstream_cnt--;
        group_rec->activated_upstream_cnt++;
    }
}

#endif /* MAODV */
